#include <stdarg.h>

#include "xmlrpc_config.h"
#include "xmlrpc-c/util.h"
#include "bool.h"
#include <stdio.h>
#include <xmlrpc-c/base.h>
#include <xmlrpc-c/client.h>
#include <xmlrpc-c/client_int.h>
#include <xmlrpc-c/client_global.h>

#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>

#include "bool.h"
#include "mallocvar.h"

#include "xmlrpc-c/base_int.h"
#include "xmlrpc-c/string_int.h"
/* transport_config.h defines XMLRPC_DEFAULT_TRANSPORT,
 MUST_BUILD_WININET_CLIENT, MUST_BUILD_CURL_CLIENT,
 MUST_BUILD_LIBWWW_CLIENT
 */
#include "transport_config.h"
#include "version.h"

#include "multicall_asynch.h"
#include "asynch_record.h"
#include <pthread.h>
/*=========================================================================
 Global Client
 =========================================================================*/

static struct xmlrpc_client * globalClientP;
static bool globalClientExists = false;

void xmlrpc_client_init2(xmlrpc_env * const envP, int const flags,
		const char * const appname, const char * const appversion,
		const struct xmlrpc_clientparms * const clientparmsP,
		unsigned int const parmSize) {
	/*----------------------------------------------------------------------------
	 This function is not thread-safe.
	 -----------------------------------------------------------------------------*/
	if (globalClientExists)
		xmlrpc_faultf(envP,
				"Xmlrpc-c global client instance has already been created "
						"(need to call xmlrpc_client_cleanup() before you can "
						"reinitialize).");
	else {
		/* The following call is not thread-safe */
		xmlrpc_client_setup_global_const(envP);
		if (!envP->fault_occurred) {
			xmlrpc_client_create(envP, flags, appname, appversion, clientparmsP,
					parmSize, &globalClientP);
			if (!envP->fault_occurred)
				globalClientExists = true;

			if (envP->fault_occurred)
				xmlrpc_client_teardown_global_const();
		}
	}
}

void xmlrpc_client_init(int const flags, const char * const appname,
		const char * const appversion) {
	/*----------------------------------------------------------------------------
	 This function is not thread-safe.
	 -----------------------------------------------------------------------------*/
	struct xmlrpc_clientparms clientparms;

	/* As our interface does not allow for failure, we just fail silently ! */

	xmlrpc_env env;
	xmlrpc_env_init(&env);

	clientparms.transport = NULL;

	/* The following call is not thread-safe */
	xmlrpc_client_init2(&env, flags, appname, appversion, &clientparms,
			XMLRPC_CPSIZE(transport));

	xmlrpc_env_clean(&env);
}

void xmlrpc_client_cleanup() {
	/*----------------------------------------------------------------------------
	 This function is not thread-safe
	 -----------------------------------------------------------------------------*/
	XMLRPC_ASSERT(globalClientExists);

	xmlrpc_client_destroy(globalClientP);

	globalClientExists = false;

	/* The following call is not thread-safe */
	xmlrpc_client_teardown_global_const();
}

static void validateGlobalClientExists(xmlrpc_env * const envP) {

	if (!globalClientExists)
		xmlrpc_faultf(envP, "Xmlrpc-c global client instance "
				"has not been created "
				"(need to call xmlrpc_client_init2()).");
}

void
xmlrpc_client_transport_call(
		xmlrpc_env * const envP,
		void * const reserved ATTR_UNUSED,
		/* for client handle */
		const xmlrpc_server_info * const serverP,
		xmlrpc_mem_block * const callXmlP,
		xmlrpc_mem_block ** const respXmlPP) {

	validateGlobalClientExists(envP);
	if (!envP->fault_occurred)
	xmlrpc_client_transport_call2(envP, globalClientP, serverP,
			callXmlP, respXmlPP);
}

static void clientCall_va(xmlrpc_env * const envP,
		const xmlrpc_server_info * const serverInfoP,
		const char * const methodName, const char * const format, va_list args,
		xmlrpc_value ** const resultPP) {

	validateGlobalClientExists(envP);
	if (!envP->fault_occurred) {
		xmlrpc_value * paramArrayP;
		const char * suffix;

		xmlrpc_build_value_va(envP, format, args, &paramArrayP, &suffix);

		if (!envP->fault_occurred) {
			if (*suffix != '\0')
				xmlrpc_faultf(envP, "Junk after the argument "
						"specifier: '%s'.  "
						"There must be exactly one arument.", suffix);
			else
				xmlrpc_client_call2(envP, globalClientP, serverInfoP,
						methodName, paramArrayP, resultPP);

			xmlrpc_DECREF(paramArrayP);
		}
	}
}

xmlrpc_value *
xmlrpc_client_call(xmlrpc_env * const envP, const char * serverUrls[],
		const int numofserver, const int semantic,
		//const xmlrpc_value * resultPs,
		const char * const methodName, const char * const format, ...) {
	int i;
	//for(i=0;i<numofserver;i++)
	//	printf("%s\n", serverUrls[i]);
	const char * const serverUrl = "http://localhost:8082/RPC2";
	xmlrpc_value * resultP;

	xmlrpc_server_info * serverInfoP;
	int numofhead = 0;
	int numoftail = 0;
	const char * const stringValueP;
	for (i = 0; i < numofserver; i++) {
		serverInfoP = xmlrpc_server_info_new(envP, serverUrls[i]);

		if (!envP->fault_occurred) {
			va_list args;
			va_start(args, format);

			clientCall_va(envP, serverInfoP, methodName, format, args,
					&resultP);

			va_end(args);
			xmlrpc_server_info_free(serverInfoP);
		//}
		//xmlrpc_array_append_item(envP, resultPs, resultP);
		xmlrpc_read_string(envP, resultP, &stringValueP);
		//}
		if (strcmp("Head", stringValueP) == 0)
			numofhead++;
		if (strcmp("Tail", stringValueP) == 0)
			numoftail++;
		}
		if (semantic == 1) //all
				{
		}
		if (semantic == 2) //any
				{
			//resultP = xmlrpc_build_value(envP, "s", "error");
			break;
		}
		if (semantic == 3) //majority
				{
			int major = numofserver / 2 + 1;
			if (numofhead >= major) {
				resultP = xmlrpc_build_value(envP, "s", "Head");
				break;
			}
			if (numoftail >= major) {
				resultP = xmlrpc_build_value(envP, "s", "Tail");
				break;
			}
		}
		//int size = xmlrpc_array_size(envP, resultPs);

	}
	if (semantic == 1) //all
			{
		if (numofhead == numofserver)
			resultP = xmlrpc_build_value(envP, "s", "Head");
		else if (numoftail == numofserver)
			resultP = xmlrpc_build_value(envP, "s", "Tail");
		else {
			resultP = xmlrpc_build_value(envP, "s", "Error");
			envP->fault_occurred = 1;
		}
	}
	return resultP;
}

/*xmlrpc_value * 
 xmlrpc_client_call(xmlrpc_env * const envP,
 const char * const serverUrl,
 const char * const methodName,
 const char * const format,
 ...) {

 xmlrpc_value * resultP;

 xmlrpc_server_info * serverInfoP;

 serverInfoP = xmlrpc_server_info_new(envP, serverUrl);

 if (!envP->fault_occurred) {
 va_list args;
 va_start(args, format);

 clientCall_va(envP, serverInfoP, methodName, format, args, &resultP);

 va_end(args);
 xmlrpc_server_info_free(serverInfoP);
 }

 return resultP;
 }*/

xmlrpc_value *
xmlrpc_client_call_server(xmlrpc_env * const envP,
		const xmlrpc_server_info * const serverP, const char * const methodName,
		const char * const format, ...) {

	va_list args;
	xmlrpc_value * resultP;

	va_start(args, format);
	clientCall_va(envP, serverP, methodName, format, args, &resultP);
	va_end(args);

	return resultP;
}

xmlrpc_value *
xmlrpc_client_call_server_params(xmlrpc_env * const envP,
		const xmlrpc_server_info * const serverInfoP,
		const char * const methodName, xmlrpc_value * const paramArrayP) {

	xmlrpc_value * resultP;

	validateGlobalClientExists(envP);

	if (!envP->fault_occurred)
		xmlrpc_client_call2(envP, globalClientP, serverInfoP, methodName,
				paramArrayP, &resultP);

	return resultP;
}

xmlrpc_value *
xmlrpc_client_call_params(xmlrpc_env * const envP, const char * const serverUrl,
		const char * const methodName, xmlrpc_value * const paramArrayP) {

	xmlrpc_value * resultP;

	validateGlobalClientExists(envP);

	if (!envP->fault_occurred) {
		xmlrpc_server_info * serverInfoP;

		serverInfoP = xmlrpc_server_info_new(envP, serverUrl);

		if (!envP->fault_occurred) {
			xmlrpc_client_call2(envP, globalClientP, serverInfoP, methodName,
					paramArrayP, &resultP);

			xmlrpc_server_info_free(serverInfoP);
		}
	}
	return resultP;
}

void xmlrpc_client_call_server_asynch_params(
		xmlrpc_server_info * const serverInfoP, const char * const methodName,
		xmlrpc_response_handler responseHandler, void * const userData,
		xmlrpc_value * const paramArrayP) {

	xmlrpc_env env;

	xmlrpc_env_init(&env);

	validateGlobalClientExists(&env);

	if (!env.fault_occurred)
		xmlrpc_client_start_rpc(&env, globalClientP, serverInfoP, methodName,
				paramArrayP, responseHandler, userData);

	if (env.fault_occurred) {
		/* Unfortunately, we have no way to return an error and the
		 regular callback for a failed RPC is designed to have the
		 parameter array passed to it.  This was probably an oversight
		 of the original asynch design, but now we have to be as
		 backward compatible as possible, so we do this:
		 */
		(*responseHandler)(serverInfoP->serverUrl, methodName, paramArrayP,
				userData, &env, NULL);
	}
	xmlrpc_env_clean(&env);
}

void xmlrpc_client_call_asynch(const char * const serverUrl,
		const char * const methodName, xmlrpc_response_handler responseHandler,
		void * const userData, const char * const format, ...) {

	xmlrpc_env env;

	xmlrpc_env_init(&env);

	validateGlobalClientExists(&env);

	if (!env.fault_occurred) {
		xmlrpc_value * paramArrayP;
		const char * suffix;
		va_list args;

		va_start(args, format);
		xmlrpc_build_value_va(&env, format, args, &paramArrayP, &suffix);
		va_end(args);

		if (!env.fault_occurred) {
			if (*suffix != '\0')
				xmlrpc_faultf(&env, "Junk after the argument "
						"specifier: '%s'.  "
						"There must be exactly one arument.", suffix);
			else
				xmlrpc_client_call_asynch_params(serverUrl, methodName,
						responseHandler, userData, paramArrayP);
		}
	}
	if (env.fault_occurred)
		(*responseHandler)(serverUrl, methodName, NULL, userData, &env, NULL);

	xmlrpc_env_clean(&env);
}

void xmlrpc_client_call_asynch_params(const char * const serverUrl,
		const char * const methodName, xmlrpc_response_handler responseHandler,
		void * const userData, xmlrpc_value * const paramArrayP) {
	xmlrpc_env env;
	xmlrpc_server_info * serverInfoP;

	xmlrpc_env_init(&env);

	serverInfoP = xmlrpc_server_info_new(&env, serverUrl);

	if (!env.fault_occurred) {
		xmlrpc_client_call_server_asynch_params(serverInfoP, methodName,
				responseHandler, userData, paramArrayP);

		xmlrpc_server_info_free(serverInfoP);
	}
	if (env.fault_occurred)
		(*responseHandler)(serverUrl, methodName, paramArrayP, userData, &env,
				NULL);
	xmlrpc_env_clean(&env);
}

void xmlrpc_client_call_server_asynch(xmlrpc_server_info * const serverInfoP,
		const char * const methodName, xmlrpc_response_handler responseHandler,
		void * const userData, const char * const format, ...) {

	xmlrpc_env env;
	xmlrpc_value * paramArrayP;
	const char * suffix;
	va_list args;

	xmlrpc_env_init(&env);

	va_start(args, format);
	xmlrpc_build_value_va(&env, format, args, &paramArrayP, &suffix);
	va_end(args);

	if (!env.fault_occurred) {
		if (*suffix != '\0')
			xmlrpc_faultf(&env, "Junk after the argument "
					"specifier: '%s'.  "
					"There must be exactly one arument.", suffix);
		else
			xmlrpc_client_call_server_asynch_params(serverInfoP, methodName,
					responseHandler, userData, paramArrayP);

		xmlrpc_DECREF(paramArrayP);
	}
	if (env.fault_occurred)
		(*responseHandler)(serverInfoP->serverUrl, methodName, NULL, userData,
				&env, NULL);

	xmlrpc_env_clean(&env);
}

void xmlrpc_client_event_loop_finish_asynch(void) {

	XMLRPC_ASSERT(globalClientExists);
	xmlrpc_client_event_loop_finish(globalClientP);
}

void xmlrpc_client_event_loop_finish_asynch_timeout(
		unsigned long const milliseconds) {

	XMLRPC_ASSERT(globalClientExists);
	xmlrpc_client_event_loop_finish_timeout(globalClientP, milliseconds);
}

/*********************************************************************
 *
 * multi-cast asynch calls
 *
 *********************************************************************/

xmlrpc_value ** globalResult;
char ** globalUrls;
int globalNumServer;
xmlrpc_response_handler globalHandler;
int globalSemantic;
asynch_record * globalCallRecords;

#define ALL 1
#define ANY 2
#define MAJORITY 3

pthread_mutex_t rv_mutex = PTHREAD_MUTEX_INITIALIZER;

void multicall_asynch(const char ** const serverUrls, int numServer,
		const char * const methodName, xmlrpc_response_handler responseHandler,
		int semantic, void * const userData, const char * const format, ...) {
	//printf("calling %s \n", methodName);
	xmlrpc_env env;

	if (!validSemantic(semantic)) {
		printf("Illegal semantic: %d. \n", semantic);
		printf("All = %d, ANY = %d, MAJORITY = %d.\n" + ALL, ANY, MAJORITY);
		exit(1);
	} else {
		globalSemantic = semantic;
	}

	xmlrpc_env_init(&env);

	validateGlobalClientExists(&env);

	if (!env.fault_occurred) {
		xmlrpc_value * paramArrayP;
		const char * suffix;
		va_list args;

		globalUrls = serverUrls;
		globalNumServer = numServer;
		globalHandler = responseHandler;

		va_start(args, format);
		xmlrpc_build_value_va(&env, format, args, &paramArrayP, &suffix);
		va_end(args);

		if (!env.fault_occurred) {
			if (*suffix != '\0')
				xmlrpc_faultf(&env, "Junk after the argument "
						"specifier: '%s'.  "
						"There must be exactly one argument.", suffix);
			else
				xmlrpc_client_multicall_asynch_params(serverUrls, numServer,
						methodName, responseHandler, userData, paramArrayP);
		}
	}
	if (env.fault_occurred)
		(*responseHandler)(serverUrls[0], methodName, NULL, userData, &env,
				NULL);

	xmlrpc_env_clean(&env);
}

void xmlrpc_client_multicall_asynch_params(const char * const serverUrls[],
		int numServer, const char * const methodName,
		xmlrpc_response_handler responseHandler, void * const userData,
		xmlrpc_value * const paramArrayP) {

	xmlrpc_env env;
	xmlrpc_server_info * serverInfoP[numServer];

	int i;

	xmlrpc_env_init(&env);

	// build the server info structure
	for (i = 0; i < numServer; i++) {
		serverInfoP[i] = xmlrpc_server_info_new(&env, serverUrls[i]);
	}

	if (!env.fault_occurred) {
		xmlrpc_client_start_multirpc(&env, globalClientP, serverInfoP,
				numServer, methodName, paramArrayP, responseHandler, userData);
		// free each server info structure
		for (i = 0; i < numServer; i++)
			xmlrpc_server_info_free(serverInfoP[i]);
	}

	if (env.fault_occurred)
		(*responseHandler)(serverUrls[0], methodName, paramArrayP, userData,
				&env, NULL);

	xmlrpc_env_clean(&env);
}

void wait_multiloop_finish() {

	xmlrpc_client * const clientP = globalClientP;

	XMLRPC_ASSERT_PTR_OK(clientP);

	clientP->transportOps.finish_asynch(clientP->transportP, timeout_no, 0);
}

void xmlrpc_client_start_multirpc(xmlrpc_env * const envP,
		struct xmlrpc_client * const clientP,
		xmlrpc_server_info * const serverInfoP[], int numServer,
		const char * const methodName, xmlrpc_value * const argP,
		xmlrpc_response_handler completionFn, void * const userData) {

	struct xmlrpc_call_info * callInfoP[numServer];
	int i;

	XMLRPC_ASSERT_ENV_OK(envP);
	XMLRPC_ASSERT_PTR_OK(clientP);
	XMLRPC_ASSERT_PTR_OK(methodName);
	XMLRPC_ASSERT_VALUE_OK(argP);

	// before you create the actual calls, create the results structures
	globalResult = (xmlrpc_value **) malloc(sizeof(xmlrpc_value*) * numServer);

	for (i = 0; i < numServer; i++) {

		XMLRPC_ASSERT_PTR_OK(serverInfoP[i]);

		callInfoCreate(envP, methodName, argP, clientP->dialect,
				serverInfoP[i]->serverUrl, completionFn, userData,
				&callInfoP[i]);

//		printf("now sending request to %s : %s.\n", serverInfoP[i]->serverUrl,
//				methodName);
		if (!envP->fault_occurred)
			clientP->transportOps.send_request(envP, clientP->transportP,
					serverInfoP[i], callInfoP[i]->serialized_xml,
					&multiAsynchComplete, callInfoP[i]);

		if (envP->fault_occurred) {
			printf("fault occurred in multi rpc");
			callInfoDestroy(callInfoP[i]);
		} else {
			/* asynchComplete() will destroy *callInfoP */
		}
	}
}

int validSemantic(int semantic) {
	if (semantic != ALL && semantic != ANY && semantic != MAJORITY)
		return 0;
	else
		return 1;

}

asynch_record new_record(int id, int numServer) {
	asynch_record record;
	record.call_id = id;
	record.results = NULL;

	return record;
}

void prepareAsynchRecords(int numCalls) {
	globalCallRecords = malloc(sizeof(asynch_record) * numCalls);
}

void destroyAsynchRecords() {
	free(globalCallRecords);
}
